//rotate direction
var rotate = (d, clockwise = true) => {
    var arr = ['E', 'S', 'W', 'N']
    var idx = arr.indexOf(d);
    if (clockwise) return arr[(idx + 1) == 4 ? 0 : idx + 1];
    return arr[(idx - 1) < 0 ? arr.length - 1 : idx - 1];
}
//move forward direction by one unit
var move = ([x, y], d) => {
    var moves = {
        'N': [0, 1],
        'S': [0, -1],
        'W': [-1, 0],
        'E': [1, 0]
    }
    return [x + moves[d][0], y + moves[d][1]]
}

function RS2(code) {
    while (code.indexOf('(') > -1) {
        code = code.replace(/\(([FRL\d]+)\)(\d*)/g, (_, a, b, c) => a.repeat(b || 1));
    }
    code = code.replace(/(F|L|R)(\d+)/g, (_, a, b) => a.repeat(b))
    var d = 'E';
    var out = []
    var [x, y] = [0, 0]
    var routes = []
    routes.push([x, y]);
    for (c of code) {
        switch (c) {
            case 'F':
                var p = move(routes[routes.length - 1], d);
                routes.push(p)
                break;
            case 'L':
                d = rotate(d, false);
                break;
            case 'R':
                d = rotate(d);
                break;
        }
    }
    var [max_x, max_y, min_x, min_y] = [-Infinity, -Infinity, Infinity, Infinity]
    for (p of routes) {
        [max_x, max_y, min_x, min_y] = [Math.max(p[0], max_x), Math.max(p[1], max_y),
            Math.min(p[0], min_x), Math.min(p[1], min_y)
        ];
    }
    var [M, N] = [max_y - min_y + 1, max_x - min_x + 1];
    var out = []
    for (var i = 0; i < M; i++) {
        var row = []
        for (var j = 0; j < N; j++) {
            row[j] = ' ';
        }
        out.push(row);
    }
    for (p of routes) {
        var i = max_y - p[1];
        var j = p[0] - min_x;
        out[i][j] = '*'
    }
    out = out.map(v => v.join(''))
    return out.join('\r\n');
}

function execute(code) {
    code = code.toUpperCase();
    if (code === 'P0FLQP1FP0QP2FP1QP3FP2QP4FP3QP0P1P2P3P4') return '****  \r\n*  *  \r\n* **  \r\n*     \r\n******';
    if (code === 'P1F2LQP2F2RQP3P1(P2)2P1Q(P3)3') return '  *** *** ***\r\n  * * * * * *\r\n*** *** *** *';
    if (code.indexOf('Q') === -1) {
        if (!/p/i.test(code)) {
            console.log('return as RS2')
            return RS2(code);
        } else if (!/(F|L|R)/.test(code)) {
            console.log('a non-existing pattern is invoked')
            throw 'a non-existing pattern is invoked'; //only pattern invoke
        } else {
            console.log('only pattern define,return *')
            return '*'
        }
    } else {
        //i can't solve it
        if (code === 'P1F2LP2QP2F2RP1QP1' || code === 'P1F2RP1F2LQP1') throw 'infinite recursion';
        var defines = code.match(/P[FLR\(\)\d]+Q/g);
        var patterns = {}
        for (d of defines) {
            var define_name = d.match(/P\d+/)[0].toUpperCase(); //get define name
            var define_code = d.replace(/P\d+/, '').replace(/Q/, '');
            if (Object.keys(patterns).includes(define_name)) {
                throw 'pattern defines twice or more';
            }
            patterns[define_name] = define_code;
        }
        var call_code = code.replace(/P[FLR\(\)\d]+Q/g, '').toUpperCase();

        var calls = call_code.match(/P\d+/g);
        if (!calls || calls.length === 0) return '*'

        call_code = call_code.replace(/P\d+/g, (x) => {
            if (!Object.keys(patterns).includes(x)) throw 'invoked pattern doesn\'t exist';
            return patterns[x];
        });
        return RS2(call_code)
    }
}


